package com.leanengine.server;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Text;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.node.ArrayNode;
import org.codehaus.jackson.node.ObjectNode;
import java.util.*;
public class JsonUtils {
private static ThreadLocal<ObjectMapper> tlObjectMapper = new ThreadLocal<ObjectMapper>();
/**
* Returns a thread-local instance of JSON ObjectMapper.
*
* @return ObjectMapper.
*/
public static ObjectMapper getObjectMapper() {
ObjectMapper objectMapper = tlObjectMapper.get();
if (objectMapper == null) {
objectMapper = initObjectMapper();
tlObjectMapper.set(objectMapper);
}
return objectMapper;
}
private static ObjectMapper initObjectMapper() {
return new ObjectMapper();
}
public static ObjectNode entityListToJson(List<Entity> entityList) throws LeanException {
ObjectNode json = getObjectMapper().createObjectNode();
ArrayNode array = getObjectMapper().createArrayNode();
for (Entity entity : entityList) {
array.add(entityToJson(entity));
}
json.put("result", array);
return json;
}
public static JsonNode entityToJson(Entity entity) throws LeanException {
ObjectNode json = getObjectMapper().createObjectNode();
json.put("_id", entity.getKey().getId());
json.putPOJO("_kind", entity.getKind());
json.putPOJO("_account", entity.getProperty("_account"));
Map<String, Object> props = entity.getProperties();
for (Map.Entry<String, Object> prop : props.entrySet()) {
addTypedNode(json, prop.getKey(), prop.getValue());
}
return json;
}
public static Map<String, Object> entityPropertiesFromJson(JsonNode jsonNode) throws LeanException {
Map<String, Object> props = new HashMap<String, Object>(jsonNode.size());
// must have some properties
if (jsonNode.size() == 0) throw new LeanException(LeanException.Error.EmptyEntity);
Iterator<String> fieldNames = jsonNode.getFieldNames();
while (fieldNames.hasNext()) {
String field = fieldNames.next();
// skip LeanEngine system properties (starting with underscore '_')
if (field.startsWith("_")) continue;
JsonNode subNode = jsonNode.get(field);
props.put(field, propertyFromJson(subNode));
}
return props;
}
public static Object propertyFromJson(JsonNode node) throws LeanException {
if (node.isObject()) {
return typedObjectFromJson((ObjectNode) node);
} else if (node.isArray()) {
return typedArrayFromJson((ArrayNode) node);
} else if (node.isLong()) {
return node.getLongValue();
} else if (node.isInt()) {
return node.getIntValue();
} else if (node.isDouble()) {
return node.getDoubleValue();
} else if (node.isBoolean()) {
return node.getBooleanValue();
} else if (node.isTextual()) {
return node.getTextValue();
} else {
throw new LeanException(LeanException.Error.ValueToJSON, " Unknown value node type.");
}
}
private static List<Object> typedArrayFromJson(ArrayNode arrayNode) throws LeanException {
List<Object> result = new ArrayList<Object>(arrayNode.size());
for (JsonNode node : arrayNode) {
result.add(propertyFromJson(node));
}
return result;
}
private static Object typedObjectFromJson(ObjectNode node) throws LeanException {
// must have 'type' field
String type = node.get("type").getTextValue();
if (type == null) throw new LeanException(LeanException.Error.ValueToJSON, " Missing 'type' field.");
if ("date".equals(type)) {
return new Date(getLongFromValueNode("value", node));
} else if ("text".equals(type)) {
return new Text(getStringFromValueNode("value", node));
} else if ("geopt".equals(type)) {
throw new IllegalArgumentException("Value nodes of type 'geopt' are not yet implemented.");
} else if ("geohash".equals(type)) {
throw new IllegalArgumentException("Value nodes of type 'geohash' are not yet implemented.");
} else if ("blob".equals(type)) {
throw new IllegalArgumentException("Value nodes of type 'blob' are not yet implemented.");
} else if ("shortblob".equals(type)) {
throw new IllegalArgumentException("Value nodes of type 'shortblob' are not yet implemented.");
} else if ("reference".equals(type)) {
throw new IllegalArgumentException("Value nodes of type 'reference' are not yet implemented.");
} else {
//unknown node type
throw new LeanException(LeanException.Error.ValueToJSON, " Unknown type '" + type + "'.");
}
}
private static long getLongFromValueNode(String fieldName, JsonNode node) throws LeanException {
return getNodeValue(fieldName, node).getLongValue();
}
private static String getStringFromValueNode(String fieldName, JsonNode node) throws LeanException {
return getNodeValue(fieldName, node).getTextValue();
}
private static JsonNode getNodeValue(String fieldName, JsonNode node) throws LeanException {
// must have 'fieldName' field
JsonNode valueNode = node.get(fieldName);
if (valueNode == null)
throw new LeanException(LeanException.Error.ValueToJSON, " Missing '" + fieldName + "' field.");
return valueNode;
}
public static void addTypedNode(ObjectNode node, String key, Object value) throws LeanException {
if (value instanceof List) {
List list = (List) value;
ArrayNode arrayNode = JsonUtils.getObjectMapper().createArrayNode();
for (Object listItem : list) {
addTypedValueToArray(arrayNode, listItem);
}
node.put(key, arrayNode);
} else {
addTypedValue(node, key, value);
}
}
private static void addTypedValueToArray(ArrayNode node, Object value) {
if (value instanceof Long) {
node.add((Long) value);
} else if (value instanceof Double) {
node.add((Double) value);
} else if (value instanceof String) {
node.add((String) value);
} else if (value instanceof Boolean) {
node.add((Boolean) value);
} else if (value instanceof Date) {
node.add(getDateNode((Date) value));
} else if (value instanceof Text) {
node.add(getTextNode((Text) value));
}
}
private static void addTypedValue(ObjectNode node, String key, Object value) {
if (value instanceof Long) {
node.put(key, (Long) value);
} else if (value instanceof Double) {
node.put(key, (Double) value);
} else if (value instanceof String) {
node.put(key, (String) value);
} else if (value instanceof Boolean) {
node.put(key, (Boolean) value);
} else if (value instanceof Date) {
node.put(key, getDateNode((Date) value));
} else if (value instanceof Text) {
node.put(key, getTextNode((Text) value));
}
}
private static ObjectNode getDateNode(Date date) {
ObjectNode dateNode = JsonUtils.getObjectMapper().createObjectNode();
dateNode.put("type", "date");
dateNode.put("value", date.getTime());
return dateNode;
}
private static ObjectNode getTextNode(Text text) {
ObjectNode dateNode = JsonUtils.getObjectMapper().createObjectNode();
dateNode.put("type", "text");
dateNode.put("value", text.getValue());
return dateNode;
}
}